package com.ikingtech.framework.sdk.web.support.handler;

import com.ikingtech.framework.sdk.context.exception.DownloadException;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.core.support.LogHelper;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ResponseWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 全局异常捕获
 *
 * @author tie yan
 */
@Slf4j
@RestControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {

    /**
     * 异常信息格式化
     */
    private static final String EXCEPTION_INFO_FORMAT = "{}[{}]" + System.lineSeparator() + "{}";

    /**
     * 拦截业务代码异常
     *
     * @param e 业务代码异常
     * @return 统一响应
     */
    @ExceptionHandler(FrameworkException.class)
    public R<Object> frameworkExceptionHandler(FrameworkException e) {
        LogHelper.info("FrameworkExceptionHandler", this.messageWrapper(e.getMessage(), e.getModuleName(), e.getStackTrace()));
        return R.failed(e.getMessage());
    }
    @ExceptionHandler(DownloadException.class)
    public ResponseEntity<R<Object>> downloadExceptionHandler(DownloadException e) {
        LogHelper.info("DownloadExceptionHandler", this.messageWrapper(e.getMessage(), e.getModuleName(), e.getStackTrace()));
        return ResponseEntity.internalServerError().body(R.failed(e.getMessage()));
    }

    @ExceptionHandler(BindException.class)
    public R<Object> bindExceptionHandler(BindException e) {
        LogHelper.info("BindExceptionHandler", this.messageWrapper(e.getMessage(), null, e.getStackTrace()));
        return R.failed(e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<Object> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        if (null == e || null == e.getFieldError()) {
            return R.failed();
        }
        LogHelper.info("MethodArgumentNotValidExceptionHandler", this.messageWrapper(e.getMessage(), null, e.getStackTrace()));
        return R.failed(e.getFieldError().getDefaultMessage());
    }

    private String messageWrapper(String message, String moduleName, StackTraceElement[] stack) {

        return Tools.Str.format(EXCEPTION_INFO_FORMAT,
                Tools.Str.isNotBlank(message) ? message : "",
                Tools.Str.isNotBlank(moduleName) ? moduleName : "",
                this.stackWrapper(stack));
    }

    private String stackWrapper(StackTraceElement[] stack) {
        if (Tools.Array.isNotBlank(stack)) {
            StringBuilder sb = new StringBuilder();
            for (StackTraceElement traceElement : stack) {
                sb.append(traceElement.toString()).append(System.lineSeparator());
            }
            return sb.toString();
        }
        return null;
    }

    @Override
    public boolean supports(MethodParameter returnType, @NonNull Class converterType) {
        return returnType.getMethodAnnotation(ResponseWrapper.class) != null;
    }

    @Override
    public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType, @NonNull Class selectedConverterType, @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) {
        return R.ok(body);
    }
}
